/*
 *  Ето така се създават легенди.
 *  http://kidicarus222.blogspot.com/2008/10/blinky-pinky-inky-and-clyde-smarter_29.html
 *  http://www.destructoid.com/blinky-inky-pinky-and-clyde-a-small-onomastic-study-108669.phtml
 *  http://home.comcast.net/~jpittman2/pacman/pacmandossier.html
 */
#ifdef MSVC
	#include "SDL.h"
    #include "SDL_ttf.h"
#else
	#include "SDL/SDL.h"
    #include "SDL/SDL_ttf.h"
#endif

#include <stdlib.h>

#include "constants.h"
#include "renderer.h"
#include "spritesheet.h"
#include "pac.h"
#include "ghosts.h"

#include "logger.h"

#define max(a,b) (a>b?a:b)

Spritesheet * tile_sprites = NULL;
Spritesheet * actor_sprites = NULL;
SDL_Surface *grid = NULL;

// Т.е. char map[ROWS][COLS], map[36][28];
char map[COLS][ROWS];
int dotsLeft;

// Показва дали играта е в пауза.
bool moving;
// Показва дали играта не е спряла поради невъзможност да продължи
// (мъртъв ПакМан, свършили точки)
bool game_stopped;

//SDL_Surface *sprites = NULL;
SDL_Surface *screen = NULL;
SDL_Event event;

// Нашите герои. Впрочем, това са прякорите на призраците.
Pacman * Pac;
Ghost * Ghosts[NUM_GHOSTS];
Blinky * Shadow;
Pinky * Ambusher;
Inky * Bashful;
Clyde * Pokey;

void dotEaten(int x, int y)
{
    if(map[x][y] == ENERGIZER)
    {
        for(int i=0;i<NUM_GHOSTS;i++)
            Ghosts[i]->scare();
        Ghost::last_scared_at = SDL_GetTicks();
    }
    map[x][y] = SPACE;
    apply_surface( TILE_SIZE * x, TILE_SIZE*y, (*tile_sprites)[SPACE], grid);
    --dotsLeft;
}

bool draw_frame()
{
    // TODO: Реално някакъв рисуващ код, plskthxbye :)
    SDL_FillRect( screen, &screen->clip_rect, SDL_MapRGB( screen->format, 0,0,0 ) );
    apply_surface( 0, 0, grid, screen );

    Pac->draw(screen);
    display_score(Pac->get_score());
                
    
    for(int i=0;i<NUM_GHOSTS;i++)
        Ghosts[i]->draw(screen);

    if( dotsLeft <= 0)
    {
        display_message("You Win!");
    }

    if(game_stopped)
    {
        display_info("Press Enter to continue.");
    }

    else if( moving == false)
    {
        display_message("PAUSED");
    }
    if( SDL_Flip( screen ) == -1 )
    {
        log_error("Unable to flip the screen buffer.");
        return false;
    }
    return true;
}

/*
 * Създаване на лабиринта.
 * */
void load_level()
{
    log_message("Creating grid...");
    SDL_Surface * tmp = CreateSurface(SCREEN_WIDTH, SCREEN_HEIGHT, screen);
    SDL_FreeSurface(grid);
    grid = SDL_DisplayFormat( tmp );
    SDL_FreeSurface( tmp );
    
    log_message("Loading level data...");
    dotsLeft = 0;
    FILE * f = fopen("level.map","rt");
    char c;
    int pos;
    for(int y = 0; y < ROWS; y++)
    {
        for(int x = 0; x < COLS; x++)
        {
            fscanf(f,"%c",&c);
            // TODO: Направи една таблица, която показва на кое поле каква му е плочката.
            // Това щеше да е *толкова* по-лесно и красиво на Питон...
            map[x][y] = 0;
            switch(c)
            {
                case CHR_WALL:
                    map[x][y] = WALL;
                    break;
                case CHR_SPACE:
                    map[x][y] = SPACE;
                    break;
                case CHR_DOT:
                    map[x][y] = DOT;
                    ++dotsLeft;
                    break;
                case CHR_ENERGIZER:
                    map[x][y] = ENERGIZER;
                    ++dotsLeft;
                    break;
                case CHR_BASE:
                    map[x][y] = BASE;
            }
        }
        fscanf(f,"%c",&c);
    }

    /* Тази част обхожда лабиринта и за всяка плочка, която е била
     * маркирана като стена проверява:
     * Колко и кои стени са съседни на тази.
     * В зависимост от това, решава какъв sprite да сложи на стената.
     * */
    log_message("Generating walls...");
    for(int y = 0; y < ROWS; y++)
    {
        for(int x = 0; x < COLS; x++)
        {
            if(map[x][y] == WALL)
            {
                int spriteNum = 0;
                for(int dir=0;dir<4;dir++)
                {
                    int _x = x + direction[dir][0];
                    int _y = y + direction[dir][1];
                    if( _x < 0 || _x > COLS || _y < 0 || _y > ROWS )
                        continue;
                    if(map[_x][_y] == WALL)
                        spriteNum += 1<<dir;
                }
                apply_surface( TILE_SIZE * x, TILE_SIZE*y, (*tile_sprites)(WALLS, spriteNum), grid);
            }
            else
            {
                apply_surface( TILE_SIZE * x, TILE_SIZE*y, (*tile_sprites)[map[x][y]], grid);
            }
        }
    }
    return;
}

void load_actors()
{
    delete Pac, Shadow, Ambusher, Bashful, Pokey;
    Pac = new Pacman(actor_sprites);
    Shadow = new Blinky(actor_sprites);
    Ambusher = new Pinky(actor_sprites);
    Bashful = new Inky(actor_sprites);
    Pokey = new Clyde(actor_sprites);
    
    Ghosts[0] = Shadow;
    Ghosts[1] = Ambusher;
    Ghosts[2] = Bashful;
    Ghosts[3] = Pokey;
}

bool load_data()
{
    // TODO: Сложи имената на файловете в конфигурационен файл, може би?
    // Измисли нещо, така че да не са пръснати из цялата програма, ок :)
    tile_sprites = new Spritesheet("Images/tile_spritesheet.png", TILE_SIZE, non_transparent);
    actor_sprites = new Spritesheet("Images/actor_spritesheet.png", ACTOR_SIZE);
    load_level();
    load_actors();

    return true;
}


bool init()
{
    log_message("Initializing SDL...");
    if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 )
    {
        log_error("Couldn't initialize SDL.");
        return false;
    }

    screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_HWSURFACE );
    if( screen == NULL )
    {
        log_error("Couldn't initialize screen.");
        return false;
    }

    if( TTF_Init() == -1 )
    {
        log_error("Couldn't initialize TTF.");
        return false;
    }

    log_message("Loading fonts...");
    
    if( set_font("fonts/DooM.ttf") == false )
    {
        log_error("Unable to load font");
        return false;
    }
    
    SDL_WM_SetCaption( "Pacmaaan!", NULL );
    
    log_message("Loading images...");
    if(!load_data())
    {
        log_error("Unable to load images.");
        return false;
    }

    log_message("Reticulating Splines...");
    return true;
}

void clean_up()
{
    // TODO: Тази функция
    log_message("Cleaning up...");
    SDL_FreeSurface(grid);
    delete tile_sprites;
    delete actor_sprites;
    TTF_Quit();
    SDL_Quit();
}

bool process_input()
{
    while(SDL_PollEvent(&event))
    {
        if( event.type == SDL_QUIT )
        {
            log_message("Caught exit signal.");
            return false;
        }
        else if(event.type == SDL_KEYUP)
        {
            if(event.key.keysym.sym == SDLK_p)
            {
                // Глобална променлива. Използва се за пауза.
                if(moving)
                {
                    log_message("Game Paused.");
                    moving = false;
                }
                else
                {
                    log_message("Game Unpaused.");
                    moving = true;
                }
            }
            if(event.key.keysym.sym == SDLK_ESCAPE)
            {
                SDL_Event quit_event;
                quit_event.type=SDL_QUIT;
                SDL_PushEvent(&quit_event);
            }
            if(event.key.keysym.sym == SDLK_RETURN)
            {
                if(game_stopped)
                {
                    load_actors();
                    // ПакМан е минал цялото ниво.
                    if(dotsLeft == 0)
                        load_level();
                }
                game_stopped = false;
            }
            if(event.key.keysym.sym == SDLK_F2)
            {
                load_actors();
                load_level();
            }
        }
    }
    if( moving )
    {
        Uint8 *keystates = SDL_GetKeyState( NULL );
        // TODO: Тази част да е по-красива?
        if(keystates[SDLK_UP])
            Pac->set_direction(UP);
        if(keystates[SDLK_DOWN])
            Pac->set_direction(DOWN);
        if(keystates[SDLK_LEFT])
            Pac->set_direction(LEFT);
        if(keystates[SDLK_RIGHT])
            Pac->set_direction(RIGHT);
    }
    return true;
}

int main( int argc, char* args[] )
{
    if(!init())
    {
        log_error("Initialization failed. Exiting...");
        exit(1);
    }

    bool done = false;
    moving = true;
    unsigned int lastFrameAt = 0;
    unsigned int now = 0;
    log_message("Now entering: main game loop.");
    while(!done)
    {

        if(!process_input())
        {
            log_message("Exiting normally...");
            exit(1);
        }

        // Ако е станало време за рисуване на нов кадър, направи го.
        // Дори и по време на пауза се рисуват кадри(евентуално за анимация)
        now = SDL_GetTicks();
        if(now - lastFrameAt > 1000.0/FRAMES_PER_SECOND)
        {
            lastFrameAt = now;
            draw_frame();
        }

        // Ако наскоро призраците са били уплашени:
        if( Ghost::last_scared_at != 0 )
        {
            if(now - Ghost::last_scared_at > Ghost::scared_time)
            {
                for(int i=0;i<NUM_GHOSTS;i++)
                    Ghosts[i]->unscare();
            }
        }
        // Движение на героите. Те действат независимо от framerate-а.
        // В самите им функции е ограничено колко бързи да са.
        if(moving && !game_stopped)
        {
            Pac->move();
            for(int i=0;i<NUM_GHOSTS;i++)
                Ghosts[i]->move();
            for(int i=0;i<NUM_GHOSTS;i++)
            {
                switch(Ghosts[i]->collision())
                {
                    case PAC_EATEN:
                        game_stopped = true;
                        break;
                    case GHOST_EATEN:
                        // TODO: Направи го да дава повече точки за няколко изядени призрака.
                        Pac->eat_ghost();
                        break;
                }
            }
        }
        if(dotsLeft == 0)
            game_stopped = true;

    }
    clean_up();
    return 0;
}
